package com.geeker.common.util;

import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import cn.hutool.poi.excel.StyleSet;
import cn.hutool.poi.excel.style.StyleUtil;
import com.geeker.common.annotation.Excel;
import com.geeker.common.annotation.Excels;
import com.geeker.common.entity.ExcelExport;
import com.geeker.common.exception.BusinessException;
import io.micrometer.common.util.StringUtils;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.SheetUtil;
import org.apache.poi.xssf.usermodel.DefaultIndexedColorMap;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import java.awt.*;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static cn.hutool.poi.excel.style.StyleUtil.createFont;

/**
 * @description: TODO
 * @date 2022/3/11 16:21
 */
public class HuExcelUtil {
    private static Logger logger = LoggerFactory.getLogger(HuExcelUtil.class);

    public static <T> List<T> getEntityList(InputStream inputStream, Class<T> clazz) {
        ExcelReader reader = ExcelUtil.getReader(inputStream);
        Map<String,String> headers = getImportHeaders(clazz);
        List<Object> objects = reader.readRow(0);
        headers.entrySet().forEach(entry -> {
            if(!objects.contains(entry.getKey())) {
                throw new BusinessException("模板错误");
            }
            reader.addHeaderAlias(entry.getKey(), entry.getValue());
        });
        return reader.readAll(clazz);
    }

    public static <T> List<T> getEntityList(InputStream inputStream, Class<T> clazz, Map<String,String> headers) {
        ExcelReader reader = ExcelUtil.getReader(inputStream);
        List<Object> objects = reader.readRow(0);
        headers.entrySet().forEach(entry -> {
            if(!objects.contains(entry.getKey())) {
                throw new BusinessException("模板错误");
            }
            reader.addHeaderAlias(entry.getKey(), entry.getValue());
        });
        return reader.readAll(clazz);
    }

    public static <T> List<T> getEntityList(MultipartFile file, Class<T> clazz, int headerRowIndex) throws IOException {
        return getEntityList(ExcelUtil.getReader(file.getInputStream()), clazz, headerRowIndex, headerRowIndex+1);
    }

    public static <T> List<T> getEntityList(ExcelReader reader, Class<T> clazz, int headerRowIndex) {
        return getEntityList(reader, clazz, headerRowIndex, headerRowIndex+1);
    }

    public static <T> List<T> getEntityList(ExcelReader reader, Class<T> clazz, int headerRowIndex, int startRowIndex) {
        List<Object> objects = reader.readRow(headerRowIndex);
        Map<String,String> headers = getImportHeaders(clazz);
        headers.entrySet().forEach(entry -> {
            if(!objects.contains(entry.getKey())) {
                throw new RuntimeException("模板错误");
            }
            reader.addHeaderAlias(entry.getKey(), entry.getValue());
        });
        return reader.read(headerRowIndex, startRowIndex, clazz);
    }

    private static <T> void readBeanExcel(Class<T> clazz, Map<String, String> headers, Map<String, Excel> fieldExcelMap) {
        List<Field> tempFields = new ArrayList<>();
        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        for (Field field : tempFields) {
            // 单注解
            if (field.isAnnotationPresent(Excel.class)) {
                Excel annotationExcel = field.getAnnotation(Excel.class);
                if (annotationExcel != null && (annotationExcel.type() == Excel.Type.ALL || annotationExcel.type() == Excel.Type.IMPORT)) {
                    String name = annotationExcel.name();
                    if(StringUtils.isBlank(name)) {
                        Schema schema = field.getAnnotation(Schema.class);
                        if(schema != null)
                            name = schema.description();
                    }
                    headers.put(name, field.getName());
                    fieldExcelMap.put(field.getName(), annotationExcel);
                }
            }
            // 多注解
            if (field.isAnnotationPresent(Excels.class)) {
                Excels attrs = field.getAnnotation(Excels.class);
                Excel[] excels = attrs.value();
                for (Excel excel : excels) {
                    if (excel != null && (excel.type() == Excel.Type.ALL || excel.type() == Excel.Type.IMPORT)) {
                        String name = field.getName();
                        if(StringUtils.isBlank(name)) {
                            Schema schema = field.getAnnotation(Schema.class);
                            if(schema != null)
                                name = schema.description();
                        }
                        headers.put(name, field.getName());
                        fieldExcelMap.put(field.getName(), excel);
                    }
                }
            }
        }
    }

    private static <T> LinkedHashMap getExportHeaders(Class<T> clazz) {
        List<Field> tempFields = new ArrayList<>();
        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        List<ExcelExport> excelExportList = new ArrayList<>();
        for (Field field : tempFields) {
            // 单注解
            if (field.isAnnotationPresent(Excel.class)) {
                Excel annotationExcel = field.getAnnotation(Excel.class);
                if (annotationExcel != null && (annotationExcel.type() == Excel.Type.ALL || annotationExcel.type() == Excel.Type.EXPORT)) {
                    String name = annotationExcel.name();
                    if(StringUtils.isBlank(name)) {
                        Schema schema = field.getAnnotation(Schema.class);
                        if(schema != null)
                            name = schema.description();
                    }
                    excelExportList.add(new ExcelExport(name, field.getName(), annotationExcel.sort()));
                }
            }
            // 多注解
            if (field.isAnnotationPresent(Excels.class)) {
                Excels attrs = field.getAnnotation(Excels.class);
                Excel[] excels = attrs.value();
                for (Excel excel : excels) {
                    if (excel != null && (excel.type() == Excel.Type.ALL || excel.type() == Excel.Type.EXPORT)) {
                        String name = field.getName();
                        if(StringUtils.isBlank(name)) {
                            Schema schema = field.getAnnotation(Schema.class);
                            if(schema != null)
                                name = schema.description();
                        }
                        excelExportList.add(new ExcelExport(name, field.getName(), excel.sort()));
                    }
                }
            }
        }
        LinkedHashMap<String,String> headers = new LinkedHashMap<>();
        List<ExcelExport> excels = excelExportList.stream().sorted(Comparator.comparing(e -> e.getSort())).collect(Collectors.toList());
        for (ExcelExport excel : excels) {
            headers.put(excel.getFieldName(), excel.getName());
        }
        return headers;
    }

    private static <T> Map<String,String> getImportHeaders(Class<T> clazz) {
        Map<String,String> headers = new HashMap<>();
        List<Field> tempFields = new ArrayList<>();
        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        for (Field field : tempFields) {
            // 单注解
            if (field.isAnnotationPresent(Excel.class)) {
                Excel annotationExcel = field.getAnnotation(Excel.class);
                if (annotationExcel != null && (annotationExcel.type() == Excel.Type.ALL || annotationExcel.type() == Excel.Type.IMPORT)) {
                    String name = annotationExcel.name();
                    if(StringUtils.isBlank(name)) {
                        Schema schema = field.getAnnotation(Schema.class);
                        if(schema != null)
                            name = schema.description();
                    }
                    headers.put(name, field.getName());
                }
            }
            // 多注解
            if (field.isAnnotationPresent(Excels.class)) {
                Excels attrs = field.getAnnotation(Excels.class);
                Excel[] excels = attrs.value();
                for (Excel excel : excels) {
                    if (excel != null && (excel.type() == Excel.Type.ALL || excel.type() == Excel.Type.IMPORT)) {
                        String name = field.getName();
                        if(StringUtils.isBlank(name)) {
                            Schema schema = field.getAnnotation(Schema.class);
                            if(schema != null)
                                name = schema.description();
                        }
                        headers.put(name, field.getName());
                    }
                }
            }
        }
        return headers;
    }

    public static <T> void exportExcel(String fileName, Class<T> clazz, Iterable<?> data) {
        LinkedHashMap<String,String> headers = getExportHeaders(clazz);
        exportExcel(fileName, headers, data);
    }

    public static <T> void exportExcel(HttpServletResponse response, String fileName, List<Map<String, Object>> dataMap) {
        ExcelWriter writer = ExcelUtil.getWriter();
        ServletOutputStream out = null;
        try {
            writer.write(dataMap, true);
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            fileName = URLEncoder.encode(fileName + ".xls", "UTF-8");
            response.setHeader("Content-disposition", "attachment;filename="+fileName+";"+"filename*=utf-8''"+fileName);
            out = response.getOutputStream();
            writer.flush(out, true);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            writer.close();
            IoUtil.close(writer);
        }
    }

    public static void exportExcel(ExcelWriter writer) {
        exportExcel(writer, "导出");
    }

    public static <T> void exportExcel(String fileName, LinkedHashMap<String,String> headers, Iterable<?> data) {
        ExcelWriter writer = ExcelUtil.getBigWriter();
        if(data.iterator().hasNext()) {
            headers.entrySet().forEach(entry -> {
                //这个添加顺序和导出顺序相同
                writer.addHeaderAlias(entry.getKey(), entry.getValue());
            });
            writer.setOnlyAlias(true);
            writer.write(data, true);
            writer.autoSizeColumnAll();
        } else {
            writer.writeHeadRow(headers.values());
            setColumnWidth(writer);
        }
        exportExcel(writer, fileName);
    }

    /**
     * 自动设置列宽，取前15 rows 作为样本，解决卡顿和不生效
     * @see SheetUtil#getColumnWidth(Sheet, int, boolean, int, int)
     */
    public static void setColumnWidth(ExcelWriter writer) {
        // 因 column 的计算依赖字体设置，so，必先设置字体后再设列宽
        //setFont(writer);
        final Sheet sheet = writer.getSheet();
        int lastRow = Math.min(writer.getRowCount(), 16) - 1;
        for (int i = 0; i < writer.getColumnCount(); i++) {
            writer.setColumnWidth(i, (int) SheetUtil.getColumnWidth(sheet, i, false, 0, lastRow));
        }
    }

    // 字体：Consolas
    private static void setFont(ExcelWriter writer) {
        final Workbook wb = writer.getWorkbook();
        final StyleSet styleSet = writer.getStyleSet();

        final Font headFont = createFont(wb, Font.COLOR_NORMAL, (short) 12, "Consolas"),
                cellFont = createFont(wb, Font.COLOR_NORMAL, (short) 11, "Consolas");
        styleSet.setFont(headFont, false);
        styleSet.setFont(cellFont, true);
    }

    public static void exportExcel(ExcelWriter writer, String fileName) {
        HttpServletResponse response = WebTools.getHttpServletResponse();
        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        try {
            fileName = URLEncoder.encode(fileName, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
            writer.flush(out, true);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭writer，释放内存
            writer.close();
            //此处记得关闭输出Servlet流
            IoUtil.close(out);
        }
    }

    /**
     * 自定义设置单元格样式
     * @param writer 	hutool-Excel写入器
     * @param x 		x_坐标
     * @param y 		y_坐标
     */
    public static void setCellStyle(ExcelWriter writer, int x, int y, IndexedColors bg) {
        CellStyle cellStyle = writer.createCellStyle(x, y);
        setCellStyle(cellStyle, bg);
    }

    public static void setCellStyle(CellStyle cellStyle, int red, int green, int blue) {
        StyleUtil.setAlign(cellStyle, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
        StyleUtil.setBorder(cellStyle, BorderStyle.THIN, IndexedColors.BLACK);
        XSSFColor color = new XSSFColor(new Color(red, green, blue), new DefaultIndexedColorMap());
        XSSFCellStyle style = (XSSFCellStyle) cellStyle;
        style.setFillForegroundColor(color);
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
    }

    public static void setCellStyle(CellStyle cellStyle, IndexedColors bg) {
        StyleUtil.setAlign(cellStyle, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
        StyleUtil.setBorder(cellStyle, BorderStyle.THIN, IndexedColors.BLACK);
        // 填充前景色(两个一起使用)
        cellStyle.setFillForegroundColor(bg.index);
        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
    }

    public static String exportExcelToDisk(ExcelWriter writer, String fileName,String outputFilePath){
        OutputStream out = null;
        try {
            out = new FileOutputStream(outputFilePath);
            writer.flush(out, true);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            // 关闭writer，释放内存
            writer.close();
        }
        return fileName;
    }

    public static XSSFFont getXSSFFont(String fontName, Short fontHeight , int red, int green, int blue){
        XSSFColor color = new XSSFColor(new Color(red,green,blue), new DefaultIndexedColorMap());
        XSSFFont font2 = new XSSFFont();
        font2.setFontName(fontName);
        if(null != fontHeight) font2.setFontHeightInPoints(fontHeight);
        font2.setColor(color);
        return font2;
    }

    public static Map<String, Sheet> readExcelMultiSheet(MultipartFile file) {
        try {
            Workbook book = new XSSFWorkbook(file.getInputStream());
            if (book == null) {
                return Collections.emptyMap();
            }
            Map<String, Sheet> result = new HashMap<>();
            for (int i = 0; i < book.getNumberOfSheets(); i++) {
                Sheet sheet = book.getSheetAt(i);
                result.put(sheet.getSheetName(), sheet);
            }
            book.close();
            return result;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
